package migrator

import (
	"errors"
	"fmt"
	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"path/filepath"
)

// MigrationStage 迁移步骤
// 可以自定义每一步需要做的迁移程序
type MigrationStage struct {
	Name    string
	Migrate func(db *gorm.DB, name string) error
}

// MigrationPlan 迁移计划
// 支持 SQL 级别的自动迁移计划
type MigrationPlan struct {
	Name        string
	SQLs        []string
	Sharding    bool
	ShardingMod int
	CanSkip     bool
}

/*// AutoMigrateFilesWithDSN 对目录中 *.sql 文件做自动迁移
func AutoMigrateFilesWithDSN(dsn, folder string) error {
	_, err := NewWithHook(dsn, func(db *gorm.DB) error {
		return AutoMigrateFiles(db, folder)
	})

	return err
}
*/

// AutoMigrateFiles 对目录中 *.sql 文件做自动迁移
func AutoMigrateFiles(db *gorm.DB, folder string) error {
	fns, err := filepath.Glob(filepath.Join(folder, "*.sql"))
	if err != nil {
		return err
	}

	plans := make([]MigrationPlan, 0, len(fns))
	for _, file := range fns {
		plan, err := wrapSQLFile(file)
		if err != nil {
			return err
		}

		plans = append(plans, *plan)
	}

	return AutoMigrate(db, plans)
}

// GenerateMigrationPlan ...
func GenerateMigrationPlan(folder string) ([]MigrationPlan, error) {
	fns, err := filepath.Glob(filepath.Join(folder, "*.sql"))
	if err != nil {
		return nil, err
	}

	plans := make([]MigrationPlan, 0, len(fns))
	for _, file := range fns {
		plan, err := wrapSQLFile(file)
		if err != nil {
			return nil, err
		}

		plans = append(plans, *plan)
	}

	return plans, nil
}

// AutoMigrate ...
// - ping
// - bootstrap
// - migrate all
func AutoMigrate(db *gorm.DB, plans []MigrationPlan) error {
	stages := make([]MigrationStage, 0, len(plans))
	for _, plan := range plans {
		stages = append(stages, wrapMigrateStage(plan))
	}

	if err := Bootstrap(db); err != nil {
		return err
	}

	return Migrate(db, stages)
}

// MigrateAll runs bootstrap, then all migrator functions listed against
// the specified database and logs any errors
func MigrateAll(db *gorm.DB, functions []func(db2 *gorm.DB) error) {
	if err := Bootstrap(db); err != nil {
		zaplog.Error("bootstrap migrator failed", zap.Any("err", err))
	}

	for _, f := range functions {
		if err := f(db); err != nil {
			zaplog.Error("migrate failed", zap.Any("err", err))
		}
	}
}

// Migrate functions or return errors
// it won't bootstrap
func Migrate(db *gorm.DB, migrations []MigrationStage) error {
	for _, m := range migrations {
		if ExistMigration(db, m.Name) {
			continue
		}

		if err := m.Migrate(db, m.Name); err != nil {
			return err
		}

		if err := SaveMigration(db, m.Name); err != nil {
			return err
		}
	}

	return nil
}

// ExistMigration checks if the migrator called migrationName has been run already
func ExistMigration(db *gorm.DB, migrationName string) bool {
	err := db.Where("name = ?", migrationName).First(&Migration{}).Error
	var found bool
	if errors.Is(err, gorm.ErrRecordNotFound) {
		zaplog.Info("skipping migrator", zap.Any("name", migrationName))
	} else {
		found = true
		zaplog.Info("running migrator", zap.Any("name", migrationName))
	}
	return found
}

// SaveMigration saves a migrator to the migrator table
func SaveMigration(db *gorm.DB, migrationName string) error {
	m := &Migration{Name: migrationName}

	if err := db.Create(m); err != nil {
		zaplog.Error("error saving record to migrations table", zap.Any("err", err))
		return fmt.Errorf("error saving record to migrations table: %s", err.Error)
	}

	return nil
}
